
package com.studiofortress.sf.util;

/**
 * This is a static class filled with a small number of common Angle utility
 * values and methods. This includes faster versions of cos and sin, however
 * these are also slightly less accurate.
 *
 * Because the rest of the SF framework uses floats for positions and movement,
 * this utility class also uses floats for representing angles. The double
 * versions of sin and cos take doubles for ease of use and not because they
 * offer added accuracy. If you want double precision then you should use the
 * functions in the java.lang.Math class.
 *
 * The abs methods are designed to shift angles so they are between a given
 * range. For example 0, 360 and 720 are all different values but all represent
 * the same angle. They work on the same idea where angles outside of the range
 * for that abs method are shifted so that they are within it.
 * 
 * @author Joseph Lenton - JosephLenton@StudioFortress.com
 */
/*
 * The fast sin and cos functions are based on:
 *     http://www.devmaster.net/forums/showthread.php?t=5784
 */
public class Angle
{
    /**
     * Pi, as a float. This is equal to 180 degrees.
     */
    public static final float PI = (float) Math.PI;

    /**
     * Half of Pi, this is equal to 90 degrees.
     */
    public static final float HALF_PI = PI / 2.0f;
    
    /**
     * 2 * Pi. This is equal to 360 degrees.
     */
    public static final float PI2 = PI*2.0f;
    
    /**
     * The multiplyer for the toRadians function.
     */
    private static final float DEGREES_TO_RADIANS = 180f / PI;
    
    /**
     * The multiplyer for the toDegrees function.
     */
    private static final float RADIANS_TO_DEGREES = 1f / DEGREES_TO_RADIANS;
    
    /**
     * This offers no extra precision over the other toDegrees function. It
     * takes a double to allow the user to avoid adding their own cast.
     * @param angle The angle, in radians, to convert to degrees.
     * @return The degree version of the given angle.
     */
    public static float toDegrees(final double angle)
    {
        return toDegrees( (float) angle );
    }

    /**
     * This converts the given angle into degrees. This is slightly faster then
     * the version provided by the java.lang.Math class.
     * @param angle The angle to convert, in radians.
     * @return The given angle converted to degrees.
     */
    public static float toDegrees(final float angle)
    {
        return angle * RADIANS_TO_DEGREES;
    }

    /**
     * The same as the other toRadians method, this offers no extra precision
     * over that version.
     * @param angle The angle, in degrees, to convert to radians.
     * @return angle converted to radians.
     */
    public static float toRadians(final double angle)
    {
        return toRadians( (float) angle );
    }

    /**
     * This converts the given angle from degrees to radians.
     * @param angle The angle, in degrees, to convert to radians.
     * @return angle converted to radians.
     */
    public static float toRadians(final float angle)
    {
        return angle * DEGREES_TO_RADIANS;
    }

    /**
     * The same as the other absAngle, however this cast the value to a float
     * before performing the operation.
     * @param angle The given angle, in radians, to move to the range -Pi to Pi.
     * @return The angle given, in radians, moved into the range of -Pi and Pi.
     */
    public static float abs(double angle)
    {
        return abs( (float) angle );
    }
    
    /**
     * Returns the given angle, but shifted so it's between the range -Pi and Pi.
     * 
     * This is useful for comparing angles as once they have both been through
     * this function you can ensure they are both within the same range of
     * values.
     *
     * @param angle The angle to shift into this range, in radians.
     * @return The given angle shifted to be in the range -Pi and Pi, in radians.
     */
    public static float abs(float angle)
    {
        if (angle < -PI) {
            return -(angle % PI2) - PI;
        } else if (angle > PI) {
            return -(angle % PI2) + PI;
        } else {
            return angle;
        }
    }
    
    /**
     * The same as abs, only this converts it to between the range of 0 and 2PI.
     * @param angle The angle to convert.
     * @return The given angle shifted so it is between 0 and 2PI.
     */
    public static float abs0(float angle)
    {
        angle %= PI2;
        
        if (angle < 0) {
            return angle + PI2;
        } else {
            return angle;
        }
    }
    
    /**
     * Finds the value closest to 0 that you'd have to turn in order to change
     * angle from fromAngle to toAngle.
     * 
     * So from 270 degrees to 60 degrees the minDiff would be 150, as
     * 270+150 = 420 (which is 60 degrees).
     *
     * This will also move the given angles so they are all in the same range,
     * from -Pi to Pi.
     *
     * <em>This however works in radians (not degrees)!</em>
     * 
     * @param fromAngle The original angle to get the difference from.
     * @param toAngle The angle to you are getting the difference too.
     * @return The minimum difference to get from the fromAngle to the toAngle.
     */
    public static float minDiff(final float fromAngle, final float toAngle)
    {
        final float diff = toAngle - fromAngle;
        final float diffAng = Angle.abs0( diff );

        if (diffAng > PI) {
            return diffAng - PI2;
        } else {
            return diffAng;
        }
    }
    
    /* These are multiplyers for the fast sin algorithm. */
    private static final float B =  4.0f / Angle.PI;
    private static final float C = -4.0f / (Angle.PI*Angle.PI);
    private static final float P = 0.225f;
    
    /**
     * The same as the other version of cos, only it will cast the given value
     * to a float before making the calculation. This offers no extra precision
     * over the float version of Angle.cos.
     * @param angle The angle to find the cos value of, in radians.
     * @return The cos value of the given angle.
     */
    public static float cos(final double angle)
    {
        return cos( (float) angle );
    }

    /**
     * This is a faster version of cos. It is however less accurate then
     * Math.cos and if further accuracy is required then cos2 or Math.cos should
     * be used instead.
     * @param angle The angle to find the cos value of, in radians.
     * @return The cos value of the given angle.
     */
    public static float cos(final float angle)
    {
        return sin( angle+HALF_PI );
    }

    /**
     * The same as cos, only it will cast the given angle to a float before use.
     * There is no added precision in this function over the float version of
     * cos.
     * @param angle The angle to find the cos of, in radians.
     * @return The cos value for the given angle.
     */
    public static float cos2(final double angle)
    {
        return cos2( (float) angle );
    }
    
    /**
     * Like sin2, this is a slower but more accurate version of cos.
     * @param angle The angle to find the cos of, in radians.
     * @return The cos value for the given angle.
     */
    public static float cos2(final float angle)
    {
        return sin2( angle+HALF_PI );
    }

    /**
     * The double version of the other Angle.sin method. This offers no extra
     * precision over that version.
     * @param angle The angle to find the cosine of, in radians.
     * @return The cosine of the given angle.
     */
    public static float sin(final double angle)
    {
        return sin( (float) angle );
    }

    /**
     * This is a fast sin function, for when you can accept a large error
     * percentage.
     *
     * @param angle The angle to find the cosine value of, in radians.
     * @return The cosine value of the given angle.
     */
    public static float sin(float angle)
    {
        angle = Angle.abs( angle );
        return B * angle + C * angle * Math.abs(angle);
    }

    /**
     * This yields the same result as the other sin2 function which takes a
     * float.
     * @param angle The angle to find the cosine of, in radians.
     * @return The cosine of the given angle.
     */
    public static float sin2(final double angle)
    {
        return sin2( (float) angle );
    }
    
    /**
     * This is a more precise version of sin, but slightly slower.
     * @param angle The angle to find the cosine of, in radians.
     * @return The cosine of the given angle.
     */
    public static float sin2(float angle)
    {
        angle = Angle.abs( angle );
        final float y = B * angle + C * angle * Math.abs(angle);
        return P * (y * Math.abs(y) - y) + y;
    }

    /**
     * This is a much faster, but less accurate version of atan2.
     * @param y
     * @param x
     * @return
     * @author Nate for this atan2: http://www.javagaming.org/index.php/topic,21135.0.html
     */
    public static float atan2(final float y, final float x)
    {
            // From: http://dspguru.com/comp.dsp/tricks/alg/fxdatan2.htm
            final float abs_y = y < 0 ? -y : y;
            final float angle;

            if (x >= 0) {
                angle = 0.7853981633974483f - 0.7853981633974483f * (x - abs_y) / (x + abs_y);
            } else {
                angle = 2.356194490192345f - 0.7853981633974483f * (x + abs_y) / (abs_y - x);
            }

            return y < 0 ? -angle : angle;
    }
    
    /**
     * No constructor.
     */
    private Angle()
    {
    }
}
